/* The file is saved in UTF-8 codepage.
 * Check: «Stereotype», Section mark-§, Copyright-©, Alpha-α, Beta-β, Smile-☺
 */
package cz.pecinovsky.english.lootp.utility;
/*******************************************************************************
 *<pre>
 * Předchozí: Není__Toto_je_nově_definovaná_třída
 *
 * Přidáno:
 *    *
 * Upraveno:
 *    *
 *</pre>
 */

//=====  Importy pro testy

import cz.pecinovsky.english.lootp.shapes.Rectangle;

import java.util.ArrayList;
import java.util.List;



////////////////////////////////////////////////////////////////////////////////
//%%+  +++++ End of ignored starting test - place for imports +++++

import java.util.HashMap;
import java.util.Map;



/*******************************************************************************
 * The instances of class {@code Direction8} represent
 * the 8 cardinal and intercardinal directions and "no direction" NULL.
 *
 * @author   Rudolf PECINOVSKY
 * @version  3.00.001
 */
public enum Direction8
{
//== VALUES OF THE ENUMERATION TYPE ============================================

    /** East direction.       */  EAST       ("E",   1,  0),
    /** North-east direction. */  NORTH_EAST ("NE",  1, -1),
    /** North direction.      */  NORTH      ("N",   0, -1),
    /** North-west direction. */  NORTH_WEST ("NW", -1, -1),
    /** West direction.       */  WEST       ("W",  -1,  0),
    /** South-west direction. */  SOUTH_WEST ("SW", -1,  1),
    /** South direction.      */  SOUTH      ("S",   0,  1),
    /** South-east direction. */  SOUTH_EAST ("SE",  1,  1),
    /** No direction.         */  NOWHERE    ("",    0,  0),
    ;



//== CONSTANT CLASS ATTRIBUTES =================================================

    /** The number of defined directions. */
    public  static final int NUM_DIRS = values().length;

    /** The mask used with logical operations insted of division modulo.
     *  The expression expect that the number of directions is a power of two
     *  plus 1 for the direction NOWHERE. */
    private static final int MASK = 7;

    /** The square root from one half for computing the diagonal distances. */
    private static final double SQR = Math.sqrt(0.5);

    /** The map converting direction names or shorcuts
     *  to the appropriate direction. */
    private static final Map<String, Direction8> name2direction =
                                  new HashMap<String,Direction8>(NUM_DIRS*3);



//== VARIABLE CLASS ATTRIBUTES =================================================

    /** Flag announcing if the use of the NOWHERE value is allowed. */
    private static boolean nowhereProhibited = false;



//== STATIC CONSTRUCTOR (CLASS INITIALIZER, STATIC INICITALIZING BLOCK) ========

    //Initialization of static fields is not feasible before definition
    //of all the values => we should perform it subsequently
    static
    {
        if (NUM_DIRS != 9) {
            throw new RuntimeException(
                "\nSource code damaged - the wrong number of directions");
        }
        for (Direction8 dir : values())  {
            name2direction.put(dir.name(),   dir);
            name2direction.put(dir.shortcut, dir);
        }
    }



//== CONSTANT INSTANCE ATTRIBUTES ==============================================

    /** Coordinate changes by movement
     *  to the next field in this direction. */
    private final int dx, dy;

    /** One or two-letter shorcut of this direction. */
    private final String shortcut;



//== VARIABLE INSTANCE ATTRIBUTES ==============================================
//== CLASS GETTERS AND SETTERS =================================================
//== OTHER NON-PRIVATE CLASS METHODS ===========================================

    /***************************************************************************
     * Vrátí vektor se čtyřmi hlavními světovými stranami.
     *
     * @return  Požadovaný vektor.
     */
    public static Direction8[] values4()
    {
        return new Direction8[] { EAST, NORTH, WEST, SOUTH };
    }


    /***************************************************************************
     * Set if the opeations with the direction {@link #NOWHERE}.
     * If these operations are prohoibited, the methods throws by such an
     * attempt the {@link java.lang.iIllegalStateException}.
     * If these operations are allowed any object "turned" to this direction
     * remains in this direction after any turn
     * and after any movement it remains in the origin position.
     *
     * @param prohibit {@code true} to prohibit the operations,
     *                 {@code false} to allow the operations
     * @return The previous setting of this property
     */
    public static boolean prohibitNowhere(boolean prohibit) {
        boolean original = nowhereProhibited;
        nowhereProhibited = prohibit;
        return original;
    }



//##############################################################################
//== CONSTUCTORS AND FACTORY METHODS ===========================================

    /***************************************************************************
     * Returns the direction with the given name or abbreviation.
     * Unfortunatelly it is not able to name this method {@code valueOf()},
     * because the compiler definex it in this class
     * and so it cannot be overriden by our version.
     *
     * @param  name  Name or abbreviation of asked direction;
     *               the name is not case sensitive
     * @return Asked direction
     * @throws IllegalArgumentException  If there is no direction with given
     *                                   name or abbreviation
     */
    public static Direction8 get(String name)
    {
        Direction8 dir = name2direction.get(name.toUpperCase());
        if (dir == null) {
            throw new IllegalArgumentException("\nThere is no direction " +
                                    "with this name or abbreviation: " + name);
        }
        return dir;
    }


    /**************************************************************************
     * Creates a new direction with given shortcut
     * and coordinate offsets to the next field in this direction.
     *
     * @param zkratka   One or two-letter shortcut
     * @param dx        The difference of horizontal coordinate
     *                  by movement to the adjacent field in this direction
     * @param dy        The difference of vertical coordinate
     *                  by movement to the adjacent field in this direction
     */
    private Direction8(String shortcut, int dx, int dy)
    {
        this.shortcut = shortcut;
        this.dx = dx;
        this.dy = dy;
    }



//== ABSTRACT METHODS ==========================================================
//== INSTANCE GETTERS AND SETTERS ==============================================

    /***************************************************************************
     * Returns the shorctu of this direction.
     *
     * @return  The askec shortcut
     */
    public String getShortName()
    {
        return shortcut;
    }

//%A+ >135

    /***************************************************************************
     * Returns {@code true} if this direction is one of the main directions,
     * which means, if it is one of {@link #EAST}, {@link #NORTH},
     * {@link #WEST}, {@link #SOUTH}.
     *
     * @return If this is one of the main directions, it returns {@code true},
     *         otherwise it returns {@code false}
     */
    public boolean isMain()
    {
        return ((this.ordinal() & 1) == 0)  &&  (this != NOWHERE);
    }

//%A-


//== OTHER NON-PRIVATE INSTANCE METHODS ========================================
//%A+ >126

    /**************************************************************************
     * Returns the position of the next field in this direction.
     *
     * @param position  Position of the current field
     * @return Position of the next field in this direction
     */
    public Position nextPosition(Position position)
    {
        verifyAllowed();
        return new Position(position.x + dx,  position.y + dy);
    }


    /**************************************************************************
     * Returns the position of the field in given distance in this direction.
     *
     * @param position  Position of the current field
     * @param distance  Vzdálenost hladnané pozice
     * @return Position of the field in the given distance in this direction
     */
    public Position nextPosition(Position position, int distance)
    {
        verifyAllowed();
        if ((dx != 0)  &&  (dy != 0)) {
            int increment = (int)(SQR*distance + 0.5);
            return new Position(position.x + increment,
                                position.y + increment);
        } else {
            return new Position(position.x + dx*distance,
                                position.y + dy*distance);
        }
    }

//%A-

    /**************************************************************************
     * Gets the x-coordinate of the field and returns the x-cordinates of
     * the next field in this direction.
     *
     * @param x Given x-coordinate
     * @return x-coordinate of the next field in this direction
     */
    public int nextX(int x)
    {
        verifyAllowed();
        return x + dx;
    }


    /**************************************************************************
     * Gets the x-coordinate of the field and returns the x-cordinate
     * of the field in the given distance in this direction.
     *
     * @param x         Given y-coordinate
     * @param distance  The distance to the asked field
     * @return x coordinate of the field in the given distance in this direction
     */
    public double nextX(int x, int distance)
    {
        verifyAllowed();
        if ((dx != 0)  &&  (dy != 0)) {
            return x + SQR*dx*distance;
        } else {
            return x + dx*distance;
        }
    }


    /**************************************************************************
     * Gets the y-coordinate of the field and returns the y-cordinate
     * of the next field in this direction.
     *
     * @param y Given x-coordinate
     * @return y-coordinate of the next field in this direction
     */
    public int nextY(int y)
    {
        verifyAllowed();
        return y + dy;
    }


    /**************************************************************************
     * Gets the y-coordinate of the field and returns the y-cordinate
     * of the field in the given distance in this direction.
     *
     * @param x         Given y-coordinate
     * @param distance  The distance to the asked field
     * @return y coordinate of the field in the given distance in this direction
     */
    public double nextY(int y, int distance)
    {
        verifyAllowed();
        if ((dx != 0)  &&  (dy != 0)) {
            return y + SQR*dy*distance;
        } else {
            return y + dy*distance;
        }
    }


    /**************************************************************************
     * Vrátí změnu vodorovné souřadnice při přesunu
     * na sousední pole v daném směru.
     *
     * @return Změna x-ové souřadnice při přesunu o jedno pole v daném směru
     */
    public int dx()
    {
        verifyAllowed();
        return dx;
    }


    /**************************************************************************
     * Vrátí změnu svislé souřadnice při přesunu
     * na sousední pole v daném směru.
     *
     * @return Změna y-ové souřadnice při přesunu o jedno pole v daném směru
     */
    public int dy()
    {
        verifyAllowed();
        return dy;
    }


    /***************************************************************************
     * Returns the direction turned by 90 degree left.
     *
     * @return Direction turned by 90 degree left
     */
    public Direction8 leftFace()
    {
        verifyAllowed();
        return (this == NOWHERE)
               ?  NOWHERE
               :  values()[MASK & (2 + ordinal())];
    }


    /***************************************************************************
     * Returns the direction turned by 90 degree right.
     *
     * @return Direction turned by 90 degree right
     */
    public Direction8 rightFace()
    {
        verifyAllowed();
        return (this == NOWHERE)
               ?  NOWHERE
               :  values()[MASK & (-2 + ordinal())];
    }


    /***************************************************************************
     * Returns the direction turned by 180 degree.
     *
     * @return Direction turned by 180 degree
     */
    public Direction8 aboutFace()
    {
        verifyAllowed();
        return (this == NOWHERE)
               ?  NOWHERE
               :  values()[MASK & (4 + ordinal())];
    }


    /***************************************************************************
     * Returns the direction turned by 45 degree left.
     *
     * @return Direction turned by 45 degree left
     */
    public Direction8 halfLeft()
    {
        verifyAllowed();
        return (this == NOWHERE)
               ?  NOWHERE
               :  values()[MASK & (1 + ordinal())];
    }


    /***************************************************************************
     * Returns the direction turned by 45 degree right.
     *
     * @return Direction turned by 45 degree right
     */
    public Direction8 halfRight()
    {
        verifyAllowed();
        return (this == NOWHERE)
               ?  NOWHERE
               :  values()[MASK & (-1 + ordinal())];
    }

//%A+ >126

    /***************************************************************************
     * Přepočítá rozměry zadané oblasti otrientované na východ
     * a umístěné v referenční oblasti
     * na nové rozměry po otočení do tohoto směru.
     *
     * @param inner Oblast, jejíž reozměry přepočítáváme a jejíž souřadnice
     *              jsou relativní vůče referenční oblasti
     * @param ref   Referenční oblast, v níž je otáčená oblast umístěna
     *              a vůči níž jsou udávány výchozí souřadnice.
     *              V této otočené oblasti má být oblast umístěna po otočení.
     *              Přitom se předpokládá, že souřadnice referenční oblasti
     *              se otočením nezmění.
     *              V tomto ohledu je ideální referenční oblast čtvercová.
     * @return Oblast s novými parametry reprezentujícími původní oblast
     * ¨       po otočení do zadaného směru;
     *         její souřadnice jsou udávány jako absolutní
     */
    public Area turnInArea(Area inner, Area ref)
    {
        int x, y, w, h;
        switch(this) //Přepočet závisí na cílovém směru
        {
            case EAST:
                x = ref.x  +  inner.x;
                y = ref.y  +  inner.y;
                w = inner.width;
                h = inner.height;
                return new Area(x, y, w, h);

            case NORTH:
                x = ref.x  +  inner.y;
                y = ref.y  -  inner.x  +  ref.width  -  inner.width;
                w = inner.height;
                h = inner.width;
                return new Area(x, y, w, h);

            case WEST:
                x = ref.x  -  inner.x  +  ref.width   -  inner.width;
                y = ref.y  -  inner.y  +  ref.height  -  inner.height;
                w = inner.width;
                h = inner.height;
                return new Area(x, y, w, h);

            case SOUTH:
                x = ref.x  -  inner.y  +  ref.height  -  inner.height;
                y = ref.y  +  inner.x;
                w = inner.height;
                h = inner.width;
                return new Area(x, y, w, h);

            default:
                throw new RuntimeException(
                        "\nIt is not possible to turn the area into " + this);
        }
    }

//%A-


//== PRIVATE AND AUXILIARY CLASS METHODS =======================================
//== PRIVATE AND AUXILIARY INSTANCE METHODS ====================================

    /***************************************************************************
     * Verify that it is not an operation
     * prohibited for direction {@link #NOWHERE}.
     *
     * @throws IllegalStateException This operation is prohibited
     *                               for the direction {@link #NOWHERE}
     */
    private void verifyAllowed()
    {
        if (nowhereProhibited  &&  (this == NOWHERE)) {
            Throwable t = new Throwable();
            StackTraceElement[] aste = t.getStackTrace();
            StackTraceElement   ste  = aste[1];
            String method = ste.getMethodName();

            throw new IllegalStateException("\nThis operation is prohibited "
                                   + "for the direction NOWHERE: " + method);
        }
    }



//== EMBEDDED AND INNER CLASSES ================================================
//== TEST CLASSES AND METHODS ==================================================
//%X+ main
    /***************************************************************************
     *
     */
    public static void testTurnArea()
    {
        //Test ptáčení oblastí
        Area   ov    = new Area  (0,  0, 200, 100);
        Area   om    = new Area  (25, 25, 100,  50);
        Rectangle big    = new Rectangle(ov.x, ov.y, ov.width, ov.height);
        Rectangle small  = new Rectangle(om.x, om.y, om.width, om.height,
                                         NamedColor.BLACK);
        for (int i = 0;   i < 4;   i++) {
            Direction8 direction = Direction8.values4()[i];
            Area nv = direction.turnInArea(ov, ov);
            Area nm = direction.turnInArea(om, ov);
            IO.inform("Otáčím se na " + direction +
                    "\nVelký: " + nv +
                    "\nMalý:  " + nm);
            small .setPosition(nm.x, nm.y);
            big.setPosition(nv.x, nv.y);
            small .setSize(nm.width, nm.height);
            big.setSize(nv.width, nv.height);
            big.paint();
            small .paint();
        }
    }


    /***************************************************************************
     *
     */
    private static void testTurnAndMove()
    {
        List<Direction8> směry = new ArrayList<>();
        for (Direction8 s : Direction8.values()) {
            System.out.println(s + " - vlevo:" + s.leftFace() + " - vpravo:" +
                    s.rightFace() + " - vzad:" + s.aboutFace() + " - nale:" +
                    s.halfLeft() + " - napra:" + s.halfRight());
            System.out.println("       dx:" + s.dx() + " - dy:" + s.dy() +
                    " - dalšíX(0):" + s.nextX(0) + " - dalšíY(0):" +
                    s.nextY(0) + " - dalšíX(0,10):" + s.nextX(0, 10) +
                    " - dalšíY(0,10):" + s.nextY(0, 10));
        }
    }


    /***************************************************************************
     *
     */
    public static void main(String[] args)
    {
//        testOtočekAPosunů();
//        testTurnArea();
    }
//%X- main
}
